/***************************************************************************
 * This code is an original creation. It is intended to be used for a one  *
 * reader, one card situation within the Micro Framework. This is intended *
 * to be used with the GemCore Serial Lite Pro evaluation board and the    *
 * Teridian 73S1209F evaluation board. This partial class consists of the  *
 * helper functions and enums and classes that are necessary to implement  *
 * the PC/SC Micro API. The actual API functions are located in            *
 * APIFunctions.cs                                                         *
 * LAST MODIFIED: 2010 April 21                                            *
 * Copyright (C) 2009 Gemalto                                              *
 ***************************************************************************/
/***************************************************************************
 *  PCSCMicro.cs is part of the PC/SC Micro API for the .NET Micro         *
 *  Framework.                                                             *
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is free software:     *
 *  you can redistribute it and/or modify it under the terms of the GNU    *
 *  General Public License as published by the Free Software Foundation,   *
 *  either version 2 of the License, or (at your option) any later version.*
 *                                                                         *
 *  The PC/SC Micro API for the .NET Micro Framework is distributed in     *
 *  the hope that it will be useful, but WITHOUT ANY WARRANTY; without even*
 *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR    *
 *  PURPOSE.  See the GNU General Public License for more details.         *
 *                                                                         *
 *  You should have received a copy of the GNU General Public License      *
 *  along with the PC/SC Micro API for the .NET Micro Framework. If not,   *
 *  see <http://www.gnu.org/licenses/>.                                    *
 ***************************************************************************/
using System;
using Microsoft.SPOT;

namespace PCSCMicro
{

    #region Not SCard Class
    /// <summary>
    /// Error types for SCard
    /// </summary>
    /// <remarks>
    /// Some error values are never used. These were included
    /// in order to keep the PC/SC Micro API compatible
    /// with the PC/SC Lite API. They were also kept in to
    /// allow additions to be made to make functionality
    /// closer to that of the PC/SC Lite API.
    /// </remarks>
    public enum SCardError
    {
        /// <summary>
        /// Command successful
        /// </summary>
        SCARD_S_SUCCESS,
        /// <summary>
        /// Command cancelled
        /// </summary>
        SCARD_E_CANCELLED,
        /// <summary>
        /// Cannot dispose handle
        /// </summary>
        SCARD_E_CANT_DISPOSE,
        /// <summary>
        /// Invalid value given
        /// </summary>
        SCARD_E_INVALID_VALUE,
        /// <summary>
        /// Invalid handle given
        /// </summary>
        SCARD_E_INVALID_HANDLE,
        /// <summary>
        /// Not enough memory
        /// </summary>
        SCARD_E_NO_MEMORY,
        /// <summary>
        /// Interface not supported
        /// </summary>
        SCARD_E_UNSUPPORTED_INTERFACE,
        /// <summary>
        /// Feature not supported
        /// </summary>
        SCARD_E_UNSUPPORTED_FEATURE,
        /// <summary>
        /// ???
        /// </summary>
        SCARD_E_NOTIMPL,
        /// <summary>
        /// Function not supported
        /// </summary>
        SCARD_E_UNSUPPORTED_FUNCTION,
        /// <summary>
        /// Insufficient buffer
        /// </summary>
        SCARD_E_INSUFFICIENT_BUFFER,
        /// <summary>
        /// Invalid ATR
        /// </summary>
        SCARD_E_INVALID_ATR,
        /// <summary>
        /// Service not available
        /// </summary>
        SCARD_E_NO_SERVICE,
        /// <summary>
        /// Invalid parameter given
        /// </summary>
        SCARD_E_INVALID_PARAMETER,
        /// <summary>
        /// Invalid target given
        /// </summary>
        SCARD_E_INVALID_TARGET,
        /// <summary>
        /// Service was stopped
        /// </summary>
        SCARD_E_SERVICE_STOPPED,
        /// <summary>
        /// RPC transport error
        /// </summary>
        SCARD_F_COMM_ERROR,
        /// <summary>
        /// Internal error
        /// </summary>
        SCARD_F_INTERNAL_ERROR,
        /// <summary>
        /// Unknown error
        /// </summary>
        SCARD_E_UNKNOWN_READER,
        /// <summary>
        /// Command timeout
        /// </summary>
        SCARD_E_TIMEOUT,
        /// <summary>
        /// Sharing violation
        /// </summary>
        SCARD_E_SHARING_VIOLATION,
        /// <summary>
        /// No smart card inserted
        /// </summary>
        SCARD_E_NO_SMARTCARD,
        /// <summary>
        /// Unknown card
        /// </summary>
        SCARD_E_UNKNOWN_CARD,
        /// <summary>
        /// Subsystem not readey
        /// </summary>
        SCARD_E_NOT_READY,
        /// <summary>
        /// System cancelled
        /// </summary>
        SCARD_E_SYSTEM_CANCELLED,
        /// <summary>
        /// Transaction failed
        /// </summary>
        SCARD_E_NOT_TRANSACTED,
        /// <summary>
        /// Reader is unavailable
        /// </summary>
        SCARD_E_READER_UNAVAILABLE,
        /// <summary>
        /// Unknown error
        /// </summary>
        SCARD_F_UNKNOWN_ERROR,
        /// <summary>
        /// Card is not supported
        /// </summary>
        SCARD_W_UNSUPPORTED_CARD,
        /// <summary>
        /// Card is unresponsive
        /// </summary>
        SCARD_W_UNRESPONSIVE_CARD,
        /// <summary>
        /// Card is unpowered
        /// </summary>
        SCARD_W_UNPOWERED_CARD,
        /// <summary>
        /// Card was reset
        /// </summary>
        SCARD_W_RESET_CARD,
        /// <summary>
        /// Card was removed
        /// </summary>
        SCARD_W_REMOVED_CARD,
        /// <summary>
        /// Card was inserted
        /// </summary>
        SCARD_W_INSERTED_CARD,
        /// <summary>
        /// Reader is unsupported
        /// </summary>
        SCARD_E_READER_UNSUPPORTED,
        /// <summary>
        /// Card is unsupported
        /// </summary>
        SCARD_E_CARD_UNSUPPORTED,
        /// <summary>
        /// Reader already exists
        /// </summary>
        SCARD_E_DUPLICATE_READER,
        /// <summary>
        /// PCI struct too small
        /// </summary>
        SCARD_E_PCI_TOO_SMALL,
        /// <summary>
        /// Card protocol mismatch
        /// </summary>
        SCARD_E_PROTO_MISMATCH,
        /// <summary>
        /// Waited too long
        /// </summary>
        SCARD_F_WAITED_TOO_LONG,
        /// <summary>
        /// Cannot find a smart card reader
        /// </summary>
        SCARD_E_NO_READERS_AVAILABLE
    }

    /// <summary>
    /// Protocols for SCard
    /// </summary>
    public enum SCardProtocol
    {
        /// <summary>
        /// T = 0 Protocol
        /// </summary>
        T0,
        /// <summary>
        /// T = 1 Protocol
        /// </summary>
        T1
    }

    /// <summary>
    /// Actions to be taken in regards to deactivating smart card
    /// </summary>
    public enum SCardDisposition
    {
        /// <summary>
        /// Do nothing to card
        /// </summary>
        LEAVE_CARD,
        /// <summary>
        /// Reset the card
        /// </summary>
        RESET_CARD,
        /// <summary>
        /// Turn off the card
        /// </summary>
        UNPOWER_CARD
    }

    /// <summary>
    /// States in which the card can be
    /// </summary>
    public enum SCardState
    {
        /// <summary>
        /// Card is absent
        /// </summary>
        ABSENT,
        /// <summary>
        /// Card is present
        /// </summary>
        PRESENT,
        /// <summary>
        /// Card is activated
        /// </summary>
        ACTIVATED,
        /// <summary>
        /// Error has occurred, card is in error state
        /// </summary>
        ERROR
    }

    /// <summary>
    /// Share modes for readers
    /// </summary>
    public enum SCardShareMode
    {
        /// <summary>
        /// Handle is allowing other handles to share card and reader
        /// </summary>
        SHARED,
        /// <summary>
        /// Handle has exclusive rights to card and reader
        /// </summary>
        EXCLUSIVE
    }

    /// <summary>
    /// Class representing the smart card and reader
    /// </summary>
    public sealed class SCardHandle
    {
        SCardProtocol protocol;
        string atr;
        bool connected;
        SCardShareMode shareMode;

        /// <summary>
        /// Constructor
        /// </summary>
        public SCardHandle()
        {
            protocol = SCardProtocol.T0;
            connected = false;
            atr = "";
            shareMode = SCardShareMode.SHARED;
        }

        /// <summary>
        /// Sets variables, changes state to connected
        /// </summary>
        /// <param name="share">Share mode to be used by Handle</param>
        /// <param name="proto">Protocol to be used by Handle</param>
        /// <param name="Atr">ATR of the card in the reader</param>
        internal void Connect(SCardShareMode share, SCardProtocol proto, string Atr)
        {
            protocol = proto;
            atr = Atr;
            shareMode = share;
            connected = true;
        }

        /// <summary>
        /// Changes to disconnected state
        /// </summary>
        internal void Disconnect()
        {
            connected = false;
        }
 
        /// <summary>
        /// Gets whether card is connected
        /// </summary>
        /// <returns>True if card is connected, false otherwise</returns>
        public bool Connected
        {
            get
            {
                return connected;
            }
        }

        /// <summary>
        /// Gets the protocol used by this SCardHandle
        /// </summary>
        /// <returns>Protocol used by this SCardHandle</returns>
        public SCardProtocol Protocol
        {
            get
            {
                return protocol;
            }
        }

        /// <summary>
        /// Gets the ATR associated with the smart card
        /// </summary>
        /// <returns>ATR associated with the smart card</returns>
        public string ATR
        {
            get
            {
                return atr;
            }
        }

        /// <summary>
        /// Gets the share mode used by this SCardHandle
        /// </summary>
        /// <returns>Share mode used by this SCardHandle</returns>
        public SCardShareMode ShareMode
        {
            get
            {
                return shareMode;
            }
        }
    }
    
    /// <summary>
    /// Class representing a context
    /// </summary>
    /// <remarks>
    /// A context in the standard PC/SC Lite API acts as a pointer
    /// to the resource manager. Since there is no resource manager,
    /// a context in the PC/SC Micro API acts as a way of accessing
    /// the rest of the functions in the API. An application must first
    /// establish a context before doing anything else with the API. A
    /// context in this definition can only be established, be released, 
    /// or check if it is valid.
    /// </remarks>
    public sealed class SCardContext
    {
        bool isValid;

        /// <summary>
        /// Constructor
        /// </summary>
        public SCardContext()
        {
            isValid = false;
        }

        /// <summary>
        /// Sets context as valid
        /// </summary>
        internal void Establish()
        {
            isValid = true;
        }

        /// <summary>
        /// Gets whether SCardContext is valid
        /// </summary>
        /// <returns>True if SCardContext is valid, false otherwise</returns>
        public bool IsValid
        {
            get 
            {
                return isValid;
            }
        }

        /// <summary>
        /// Sets context as invalid
        /// </summary>
        internal void Release()
        {
            isValid = false;
        }
    }

    /// <summary>
    /// Class representing return of Status and GetStatusChange
    /// </summary>
    public sealed class SCardStatusReturn
    {
        string atr;
        SCardProtocol protocol;
        SCardState state;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="status">Status of card</param>
        /// <param name="Atr">ATR of card</param>
        /// <param name="proto">Protocol used with SCardHandle</param>
        public SCardStatusReturn(SCardState status, string Atr, SCardProtocol proto)
        {
            atr = Atr;
            protocol = proto;
            state = status;
        }

        /// <summary>
        /// Gets the ATR associated with the smart card
        /// </summary>
        /// <returns>ATR associated with the smart card</returns>
        public string ATR
        {
            get
            {
                return atr;
            }
        }

        /// <summary>
        /// Gets the protocol used by this SCardHandle
        /// </summary>
        /// <returns>Protocol used by this SCardHandle</returns>
        public SCardProtocol Protocol
        {
            get
            {
                return protocol;
            }
        }
        
        /// <summary>
        /// Gets the status of the card
        /// </summary>
        /// <returns>Status of the card</returns>
        public SCardState State
        {
            get
            {
                return state;
            }
        }

        /// <summary>
        /// Equals method for SCardStatusReturn objects
        /// </summary>
        /// <param name="value">Second SCardStatusReturn object</param>
        /// <returns>True if two SCardStatusReturn objects are equal,
        /// false if second object is not a SCardStatusReturn object
        /// or if they are not equal</returns>
        public bool Equals(SCardStatusReturn value)
        {
            try
            {
                return (value.ATR == atr) && (value.Protocol == protocol)
                     && (value.State == state);
            }
            catch
            {
                return false;
            }
        }
    }
        
    #endregion
    
    /// <summary>
    /// This class is used to call all of the PC/SC Micro API functions
    /// </summary>
    /// <remarks>
    /// <para>
    /// This API assumes a working version of Device.cs
    /// exists in the project with all of the necessary functions.
    /// It was created to be used on the Teridian 73S1209F eval board
    /// and the GemCore Serial Lite Pro eval board.  The API can be ported
    /// onto other reader chips if a Device.cs file is created for them that
    /// is a child of the Reader.cs class. All of the public functions are
    /// static, so no SCard object needs to be created. 
    /// </para>
    /// <para>
    /// Connection to the Smart Card reader and card take place as follows:
    /// <list type="bullet">
    /// <item>
    /// EstablishContext opens the connection to the reader.
    /// </item>
    /// <item>
    /// Connect powers the card if not already powered by another context.
    /// </item>
    /// <item>
    /// Disconnect does the action provided in the parameters.
    /// </item>
    /// <item>
    /// Reconnect applies the disposition provided in the parameters,
    /// and should be called if another context resets the card. 
    /// </item>
    /// <item>
    /// ReleaseContext actually disconnects the connection to the reader
    /// but only if it is last context to be released.
    /// </item>
    /// </list>
    /// </para>
    /// This allows status calls from GetStatusChange to be made even
    /// when Connect has not been called, or disconnect has been called.
    /// (i.e. allows user to wait for card to be present in reader before Connect is called)
    /// <para>
    /// This API creates one card for each context, thus each context
    /// has a matching card and vice versa. 
    /// If a Status call is required before Connect() is called,
    /// GetStatusChange(context, 0) can be used because the context
    /// only has access to the one card.
    /// Errors are not returned from the functions. Instead, GetError()
    /// is a new function to access the error from the most recent function call. 
    /// </para>
    /// <para>
    /// Since there is only one reader and one card, many of the
    /// functions in the PC/SC Lite API are not implemented in the PC/SC Micro API.
    /// The following is a list of excluded functions and reasons for exclusion:
    /// <list type="bullet">
    /// <listheader>
    /// <term>
    /// Excluded functions
    /// </term>
    /// <description>List of excluded functions and reason for exclusion</description>
    /// </listheader>
    /// <item>
    /// GetReaderGroups: Only one reader
    /// </item>
    /// <item>
    /// GetReaders: Only one reader.
    /// </item>
    /// <item>
    /// FreeMemory: Micro Framework only supports C# which has its own garbage collector.
    /// </item>
    /// <item>
    /// GetAttrib: Only one reader, so attributes should be known
    /// </item>
    /// <item>
    /// SetAttrib: Only one reader, so attributes should be known 
    /// </item>
    /// <item>
    /// SetTimeout: Function does not do anything in PC/SC Lite API
    /// </item>
    /// <item>
    /// CancelTransition: Function is considered bad coding technique in PC/SC Lite API
    /// </item>
    /// </list>
    /// </para>
    /// </remarks>
    public static partial class SCard
    {
        // This function is a helper function for GetStatusChange
        // which allows a new thread to run in which a change in
        // status will be found
        static void GetStatusChangeHelper()
        {
            int status, status2;
            // Get original status
            status = GetStatus();
            // Return if Cancel function has been called
            while (getStatus && !cancel)
            {
                // Return if original status differs from updated status
                status2 = GetStatus();
                if (status2 != status)
                {
                    return;
                }
            }
            return;
        }

        // Checks to make sure context is a valid Context
        static void CheckContext(SCardContext context)
        {
            if ((context == null) || (!context.IsValid)
                || (!contexts.Contains(context)) || (!ReaderDevice.IsReaderActive()))
            {
                Error = SCardError.SCARD_E_INVALID_HANDLE;
                return;
            }
            Error = SCardError.SCARD_S_SUCCESS;
        }

        // Checks to make sure card is a valid Handle
        static void CheckHandle(SCardHandle card)
        {
            if ((card == null) || (!card.Connected) || !(cards.Contains(card)))
            {
                Error = SCardError.SCARD_E_INVALID_HANDLE;
                return;
            }
            Error = SCardError.SCARD_S_SUCCESS;
        }

        // Checks if card has the permission it needs
        // whether it has exclusive access, or none do
        static void CheckPermission(SCardHandle card)
        {
            if (cards.IndexOf(card) != permission && permission != -1)
            {
                Error = SCardError.SCARD_E_SHARING_VIOLATION;
                return;
            }
            Error = SCardError.SCARD_S_SUCCESS;
        }

        // Checks if context has the permission it needs
        // whether it has exclusive access, or none do
        static void CheckPermission(SCardContext context)
        {
            if (cards.IndexOf(context) != permission && permission != -1)
            {
                Error = SCardError.SCARD_E_SHARING_VIOLATION;
                return;
            }
            Error = SCardError.SCARD_S_SUCCESS;
        }

        // Checks to make sure share is a valid ShareMode
        static void CheckShareMode(SCardShareMode share)
        {
            if ((share != SCardShareMode.EXCLUSIVE) && (share != SCardShareMode.SHARED))
            {
                Error = SCardError.SCARD_E_INVALID_VALUE;
                return;
            }
            Error = SCardError.SCARD_S_SUCCESS;
        }

        // Checks if card has been reset
        static void CheckReset(SCardHandle card)
        {
            if (cards.IndexOf(card) < resetCard.Length && resetCard[cards.IndexOf(card)])
            {
                Error = SCardError.SCARD_W_RESET_CARD;
                return;
            }
            Error = SCardError.SCARD_S_SUCCESS;
        }

        // Check to make sure disposition is valid Disposition
        // Then execute disposition action
        static void Disposition(SCardDisposition disposition)
        {
            // Check disposition value
            if ((disposition != SCardDisposition.LEAVE_CARD)
                    && (disposition != SCardDisposition.RESET_CARD)
                    //&& (disposition != SCardDisposition.EJECT_CARD) 
                    && (disposition != SCardDisposition.UNPOWER_CARD))
            {
                Error = SCardError.SCARD_E_INVALID_VALUE;
                return;
            }
            // Execute disposition action
            switch (disposition)
            {
                // RESET_CARD
                case SCardDisposition.RESET_CARD:
                    ResetCard();
                    if (Error != SCardError.SCARD_S_SUCCESS)
                    {
                        Error = SCardError.SCARD_E_NO_SMARTCARD;
                        return;
                    }
                    break;
                // UNPOWER_CARD
                case SCardDisposition.UNPOWER_CARD:
                    DeactivateCard();
                    if (Error != SCardError.SCARD_S_SUCCESS)
                    {
                        Error = SCardError.SCARD_E_NO_SMARTCARD;
                        return;
                    }
                    break;
                // LEAVE_CARD
                default:
                    break;
            }
            Error = SCardError.SCARD_S_SUCCESS;
        }

        // Checks to make sure protocol is a valid Protocol
        static void CheckProtocol(SCardProtocol protocol)
        {
            if ((protocol != SCardProtocol.T0) && (protocol != SCardProtocol.T1))
            {
                Error = SCardError.SCARD_E_INVALID_VALUE;
                return;
            }
            Error = SCardError.SCARD_S_SUCCESS;
        }

        // Calls Device's ResetCard
        static void ResetCard()
        {
            ReaderDevice.ResetCard();
            CheckError(ReaderDevice.Error);
            // Set SCard_W_ResetCard flags
            resetCard = new bool[contexts.Count];
            for (int i = 0; i < resetCard.Length; i++)
            {
                resetCard[i] = true;
            }
        }

        // Calls Device's DeactivateCard
        static void DeactivateCard()
        {
            ReaderDevice.DeactivateCard();
            CheckError(ReaderDevice.Error);
            // Set SCard_W_ResetCard flags
            resetCard = new bool[contexts.Count];
            for (int i = 0; i < resetCard.Length; i++)
            {
                resetCard[i] = true;
            }
        }

        // Calls Device's ActivateCard
        static void ActivateCard()
        {
            ReaderDevice.ActivateCard();
            CheckError(ReaderDevice.Error);
        }

        // Calls Device's ResetReader
        static void ResetReader()
        {
            ReaderDevice.ResetReader();
            CheckError(ReaderDevice.Error);
            // Set SCard_W_ResetCard flags
            resetCard = new bool[contexts.Count];
            for (int i = 0; i < resetCard.Length; i++)
            {
                resetCard[i] = true;
            }
        }

        // Calls Device's SendPPS
        static void SendPPS(SCardProtocol protocol)
        {
            // Convert SCardProtocol to byte representing protocol
            byte b1;
            if (protocol == SCardProtocol.T0)
            {
                b1 = 0;
            }
            else
            {
                b1 = 1;
            }
            ReaderDevice.SendPPS(b1);
            CheckError(ReaderDevice.Error);
        }

        // Calls Device's SendAPDU
        // Returns response from reader
        static string SendAPDU(string APDU)
        {
            string response = ReaderDevice.SendAPDU(APDU);
            CheckError(ReaderDevice.Error);
            return response;
        }
        
        // Calls Device's ExecuteCommand
        // Returns response from reader
        static string ExecuteCommand(string commandCode, string data)
        {
            string response = ReaderDevice.ExecuteCommand(commandCode, data);
            CheckError(ReaderDevice.Error);
            return response;
        }

        // Calls Device's GetStatus
        // Returns int representing card status
        static int GetStatus()
        {
            int status = ReaderDevice.GetStatus();
            CheckError(ReaderDevice.Error);
            return status;
        }

        // Takes Reader.ReaderError and returns corresponding SCardError
        static void CheckError(Reader.ReaderError err)
        {
            if (err == Reader.ReaderError.NONE)
            {
                Error = SCardError.SCARD_S_SUCCESS;
            }
            else if (err == Reader.ReaderError.SHARING_VIOLATION)
            {
                Error = SCardError.SCARD_E_SHARING_VIOLATION;
            }
            else if (err == Reader.ReaderError.TEST_RUNNING)
            {
                Error = SCardError.SCARD_E_READER_UNAVAILABLE;
            }
            else if (err == Reader.ReaderError.ACTIVATING_CARD)
            {
                Error = SCardError.SCARD_E_READER_UNAVAILABLE;
            }
            else if (err == Reader.ReaderError.ALREADY_CONNECTED)
            {
                Error = SCardError.SCARD_S_SUCCESS;
            }
            else if (err == Reader.ReaderError.BAD_COMMAND)
            {
                Error = SCardError.SCARD_E_NOT_TRANSACTED;
            }
            else if (err == Reader.ReaderError.BAD_MESSAGE)
            {
                Error = SCardError.SCARD_F_COMM_ERROR;
            }
            else if (err == Reader.ReaderError.BAD_PROTOCOL)
            {
                Error = SCardError.SCARD_E_PROTO_MISMATCH;
            }
            else if (err == Reader.ReaderError.CARD_DEACTIVATED)
            {
                Error = SCardError.SCARD_W_RESET_CARD;
            }
            else if (err == Reader.ReaderError.NO_CARD_INSERTED)
            {
                Error = SCardError.SCARD_W_REMOVED_CARD;
            }
            else if (err == Reader.ReaderError.NO_RESPONSE)
            {
                Error = SCardError.SCARD_W_UNRESPONSIVE_CARD;
            }
            else if (err == Reader.ReaderError.NO_TEST_RUNNING)
            {
                Error = SCardError.SCARD_F_UNKNOWN_ERROR;
            }
            else if (err == Reader.ReaderError.PORT_CONNECTION_FAILED)
            {
                Error = SCardError.SCARD_E_READER_UNAVAILABLE;
            }
            else if (err == Reader.ReaderError.READER_OFF)
            {
                Error = SCardError.SCARD_E_READER_UNAVAILABLE;
            }
            else if (err == Reader.ReaderError.UNKNOWN_ERROR)
            {
                Error = SCardError.SCARD_F_UNKNOWN_ERROR;
            }
            else if (err == Reader.ReaderError.CARD_MALFUNCTION)
            {
                Error = SCardError.SCARD_W_UNRESPONSIVE_CARD;
            }
            else
            {
                Error = SCardError.SCARD_F_UNKNOWN_ERROR;
            }
        }
    }
}
